/*
 * Galaxium Messenger
 * Copyright (C) 2007-2008 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Threading;
using System.Collections.Generic;

using Mono.Addins;
using Anculus.Core;
using Galaxium.Core;

namespace Galaxium.Client
{
	/// <summary>
	/// Utility used to maintain song information events and polling using
	/// the available backends.
	/// </summary>
	public static class SongPlayingUtility
	{
		public static event EventHandler<SongInformationEventArgs> SongChanged;
		
		private static IList<ISongInformationBackend> _backends;
		private static IList<ISongInformationBackend> _enabledBackends;
		
		private static ISongInformationBackend _currentBackend;
		private static SongInformation _currentSong;
		
		private static bool _enableSongInformation;
		private static bool _pollAllBackends;
		
		private static Thread _thread;
		private static object _sync = new object ();
		
		public static void Initialize ()
		{
			_backends = new List<ISongInformationBackend> ();
			_enabledBackends = new List<ISongInformationBackend> ();

			IConfigurationSection section = ConfigurationUtility.Plugins["SongInformation"];
			
			_enableSongInformation = section.GetBool ("EnableSongInformation", false);
			_pollAllBackends = section.GetBool ("PollAllBackends", true);
			
			IConfigurationSection backends = section["Backends"];
			
			foreach (SongInformationBackendExtension ext in AddinManager.GetExtensionNodes ("/Galaxium/Backends/SongInformation")) {
				ISongInformationBackend backend = ext.SongInformationBackend;
				if (backend != null) {
					backend.Initialize ();
					
					backend.SongChanged += BackendSongChanged;
					_backends.Add (backend);
					
					bool backendEnabled = backends.GetBool (backend.Name, true);
					if (backendEnabled)
						_enabledBackends.Add (backend);
				}
			}
			
			if (_enableSongInformation)
				StartListening ();
		}
		
		public static void Unload ()
		{
			lock (_sync) {
				foreach (ISongInformationBackend backend in _backends)
					backend.Unload ();
			}
		}
		
		public static IEnumerable<ISongInformationBackend> Backends
		{
			get { return _backends; }
		}
		
		public static IEnumerable<ISongInformationBackend> EnabledBackends
		{
			get { return _enabledBackends; }
		}
		
		public static bool EnableSongInformation
		{
			get { return _enableSongInformation; }
			set {
				if (_enableSongInformation != value) {
					_enableSongInformation = value;
					
					IConfigurationSection section = ConfigurationUtility.Plugins["SongInformation"];
					section.SetBool ("EnableSongInformation", value);
					
					if (value)
						StartListening ();
					else
						StopListening ();
				}
			}
		}
		
		public static bool PollAllBackends
		{
			get { return _pollAllBackends; }
			set {
				if (_pollAllBackends != value) {
					_pollAllBackends = value;
					
					IConfigurationSection section = ConfigurationUtility.Plugins["SongInformation"];
					section.SetBool ("PollAllBackends", value);
				}
			}
		}
		
		public static void ChangeBackendState (ISongInformationBackend backend, bool enabled)
		{
			if (backend == null)
				throw new ArgumentNullException ("backend");
			
			lock (_sync) {
				if (!enabled && _currentBackend == backend)
					_currentBackend = null;
				
				if (_enabledBackends.Contains (backend) && !enabled)
					_enabledBackends.Remove (backend);
				else if (!_enabledBackends.Contains (backend) && enabled)
					_enabledBackends.Add (backend);
				
				IConfigurationSection section = ConfigurationUtility.Plugins["SongInformation"]["Backends"];
				section.SetBool (backend.Name, enabled);
			}
		}
		
		public static bool IsBackendEnabled (ISongInformationBackend backend)
		{
			if (backend == null)
				throw new ArgumentNullException ("backend");
			
			return _enabledBackends.Contains (backend);
		}

		private static ISongInformationBackend FindActiveSongInformationBackend ()
		{
			IEnumerable<ISongInformationBackend> enumerable = null;
			
			if (_pollAllBackends)
				enumerable = _backends;
			else
				enumerable = _enabledBackends;
			
			lock (_sync) {
				foreach (ISongInformationBackend backend in enumerable) {
					if (backend.IsAvailable ())
						return backend;
				}
			}
			return null;
		}
		
		private static void StartListening ()
		{
			_thread = new Thread (new ThreadStart (StartListeningAsync));
			_thread.Priority = ThreadPriority.Lowest;
			_thread.IsBackground = true;
			_thread.Start ();
		}
		
		private static void StartListeningAsync ()
		{
			bool backendActive = false;
			_currentBackend = null;
			
			while (true) {
				while (!backendActive) {
					_currentBackend = FindActiveSongInformationBackend ();
					backendActive = _currentBackend != null;
					
					if (!backendActive) {
						IConfigurationSection section = ConfigurationUtility.Plugins["SongInformation"];
						int backendPollTime = section.GetInt ("BackendPollTime", 60) * 1000;
						if (backendPollTime < 20000) {
							backendPollTime = 20000;
							Log.Warn ("Overwriting BackendPollTime: {0}s is ridiculously low, using 20s instead", backendPollTime  / 1000);
						}
						Thread.Sleep (backendPollTime);
					}
				}
				
				while (backendActive) {
					if (_currentBackend != null && _currentBackend.RequiresPolling) {
						SongInformation oldSong = _currentSong;
						_currentSong = _currentBackend.GetCurrentSong ();

						if (_currentSong != oldSong && _currentSong != null) {
							ThreadUtility.Dispatch (new VoidDelegate (delegate
							{
								SongInformationEventArgs args = new SongInformationEventArgs (_currentSong);
								BackendSongChanged (_currentBackend, args);
							}));
						}
					}
					
					backendActive = _currentBackend != null && _currentBackend.IsAvailable ();
					
					if (backendActive) {
						IConfigurationSection section = ConfigurationUtility.Plugins["SongInformation"];
						int songPollTime = section.GetInt ("SongPollTime", 30) * 1000;
						if (songPollTime < 10000) {
							songPollTime = 10000;
							Log.Warn ("Overwriting SongPollTime: {0}s is ridiculously low, using 10s instead", songPollTime  / 1000);
						}
						Thread.Sleep (songPollTime);
					}
				}
			}
		}
		
		private static void BackendSongChanged (object sender, SongInformationEventArgs args)
		{
			if (SongChanged != null)
				SongChanged (sender, args);
		}
		
		private static void StopListening ()
		{
			_currentBackend = null;
			
			if (_thread != null) {
				try {
					_thread.Abort ();
				} catch {
				} finally {
					_thread = null;
				}
			}
		}
	}
}